package rjeutsky.database.jpa;

import java.sql.SQLException;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import org.hibernate.Criteria;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.Restrictions;

import rjeutsky.database.interfaces.CRUDDomainRepository;
import rjeutsky.database.interfaces.Repository;
import rjeutsky.database.jpa.models.JPAPublication;
import rjeutsky.database.jpa.models.JPASubscription;
import rjeutsky.database.jpa.models.JPAUser;
import rjeutsky.database.jpa.models.JPAUserRole;
import rjeutsky.models.Payment;
import rjeutsky.models.Publication;
import rjeutsky.models.Subscription;
import rjeutsky.models.User;
import rjeutsky.models.UserRole;

public class JPARepository implements Repository {

	private SessionFactory sessionFactory;
	private CRUDDomainRepository<Payment> paymentRepository;
	private CRUDDomainRepository<Publication> publicationRepository;
	private CRUDDomainRepository<Subscription> subscriptionRepository;
	private CRUDDomainRepository<User> userRepository;
	private CRUDDomainRepository<UserRole> userRoleRepository;
	
	public JPARepository(SessionFactory sessionFactory) {
		this.sessionFactory = sessionFactory;
		paymentRepository = new JPAPaymentRepository(sessionFactory);
		publicationRepository = new JPAPublicationRepository(sessionFactory);
		subscriptionRepository = new JPASubscriptionRepository(sessionFactory);
		userRepository = new JPAUserRepository(sessionFactory);
		userRoleRepository = new JPAUserRoleRepository(sessionFactory);
	}
	
	@Override
	public CRUDDomainRepository<Payment> getPaymentRepository() {
		return paymentRepository;
	}

	@Override
	public CRUDDomainRepository<Publication> getPublicationRepository() {
		return publicationRepository;
	}

	@Override
	public CRUDDomainRepository<User> getReaderRepository() {
		return userRepository;
	}

	@Override
	public CRUDDomainRepository<Subscription> getSubscriptionRepository() {
		return subscriptionRepository;
	}

	@Override
	public CRUDDomainRepository<UserRole> getUserRoleRepository() {
		return userRoleRepository;
	}

	@Override
	public void addPublicationToSubscription(Publication publication,
			Subscription subscription) throws SQLException {
		Session session = sessionFactory.openSession();
		session.beginTransaction();
		
		JPASubscription jpaSubscription = JPASubscription.getJPASubscription(session, subscription);
		JPAPublication jpaPublication = JPAPublication.getJPAPublication(session, publication);
		if (jpaSubscription != null && jpaPublication != null) {
			jpaSubscription.getPublications().add(jpaPublication);
		}
		
		session.getTransaction().commit();
		session.close();
	}

	@Override
	public List<Publication> getSubscriptionPublications(
		Subscription subscription) throws SQLException {
		Session session = sessionFactory.openSession();
		session.beginTransaction();
		
		List<Publication> publications;
		JPASubscription jpaSubscription = JPASubscription.getJPASubscription(session, subscription);
		if (jpaSubscription != null) {
			publications = JPAPublication.getPublications(jpaSubscription.getPublications());
		} else {
			publications = new LinkedList<Publication>();
		}
		
		session.getTransaction().commit();
		session.close();
		return publications;
	}

	@Override
	public boolean isLoginUnique(String login) throws SQLException {
		Session session = sessionFactory.openSession();
		session.beginTransaction();
		
		Criteria criteria = session.createCriteria(JPAUser.class);
		criteria.add(Restrictions.eq("login", login));
		int usersCount = criteria.list().size();
		
		session.getTransaction().commit();
		session.close();
		return usersCount == 0;
	}

	@Override
	public List<Publication> getPublicationsByReader(User reader)
			throws SQLException {
		Session session = sessionFactory.openSession();
		session.beginTransaction();
		
		JPAUser jpaUser = JPAUser.getJPAUser(session, reader);
		List<JPASubscription> subscriptions = jpaUser.getSubscriptions();
		List<Publication> publications = new LinkedList<Publication>();
		Set<Long> idSet = new HashSet<Long>();
		for (JPASubscription subscription : subscriptions) {
			for (JPAPublication publication : subscription.getPublications()) {
				if (!idSet.contains(publication.getId())) {
					idSet.add(publication.getId());
					publications.add(publication.getPublication());
				}
			}
		}
				
		session.getTransaction().commit();
		session.close();
		return publications;
	}

	@Override
	public User getUserByLogin(String login) throws SQLException {
		Session session = sessionFactory.openSession();
		session.beginTransaction();
		
		Criteria criteria = session.createCriteria(JPAUser.class);
		criteria.add(Restrictions.eq("login", login));
		@SuppressWarnings("unchecked")
		List<JPAUser> users = criteria.list();
		if (users.size() > 0) {
			return users.get(0).getUser();
		}
		
		session.getTransaction().commit();
		session.close();
		return null;
	}

	@Override
	public List<Subscription> getUnpayedSubscriptions() throws SQLException {
		Session session = sessionFactory.openSession();
		session.beginTransaction();
		 
		List<Subscription> subscriptions;
		Query query = session.createQuery("from JPASubscription s where size(s.payments) = 0");
		@SuppressWarnings("unchecked")
		List<JPASubscription> resultList = query.list();
		subscriptions = JPASubscription.getSubscriptions(resultList);
		if (subscriptions == null) {
			subscriptions = new LinkedList<Subscription>();
		}
				
		session.getTransaction().commit();
		session.close();
		return subscriptions;
	}

	@Override
	public boolean processPayment(Subscription subscription)
			throws SQLException {
		
		Session session = sessionFactory.openSession();
		session.beginTransaction();
		
		boolean result = true;
		JPASubscription jpaSubscription = JPASubscription.getJPASubscription(session, subscription);
		if (jpaSubscription.getPayments().size() > 0) {
			result = false;
		}
		
		session.getTransaction().commit();
		session.close();
		if (result) {
			long paymentAmount = getPaymentAmount(subscription);
			Payment payment = new Payment();
			payment.setPayedSum(paymentAmount);
			payment.setSubscriptionId(subscription.getId());
			paymentRepository.create(payment);
		}
		
		return true;
	}

	@Override
	public long getPaymentAmount(Subscription subscription) throws SQLException {
		Session session = sessionFactory.openSession();
		session.beginTransaction();
		
		long value = 0;
		Query query = session.createQuery("select sum (p.price) from JPAPublication as p left join p.subscriptions as s where s.id = ?");
		query.setLong(0, subscription.getId());
		@SuppressWarnings("unchecked")
		List<Long> values = query.list();
		if (values.size() > 0) {
			value = values.get(0);
		}
		
		session.getTransaction().commit();
		session.close();
		return value;
	}

	@Override
	public long getPredictedPaymentAmount(Collection<Publication> publications)
			throws SQLException {
		Long[] ids = new Long[publications.size()];
		int i = 0;
		for (Publication publication : publications) {
			ids[i++] = publication.getId();
		}
		
		Session session = sessionFactory.openSession();
		session.beginTransaction();
		
		Query query = session.createQuery("select sum (price) from JPAPublication where id in (:ids)");
		query.setParameterList("ids", ids);
		@SuppressWarnings("unchecked")
		List<Long> values = query.list();
		long value = 0;
		if (values.size() > 0) {
			value = values.get(0);
		}
		
		session.getTransaction().commit();
		session.close();
		return value;
	}

	@Override
	public List<UserRole> getUserRoles(User user) throws SQLException {
		Session session = sessionFactory.openSession();
		session.beginTransaction();
		
		List<UserRole> roles;
		JPAUser jpaUser = JPAUser.getJPAUser(session, user);
		if (jpaUser != null) {
			roles = JPAUserRole.getUserRoles(jpaUser.getRoles());
		} else {
			roles = new LinkedList<UserRole>();
		}
		
		session.getTransaction().commit();
		session.close();
		return roles;
	}

	@Override
	public void addRoleToUser(User user, UserRole role) throws SQLException {
		Session session = sessionFactory.openSession();
		session.beginTransaction();
		
		JPAUser jpaUser = JPAUser.getJPAUser(session, user);
		JPAUserRole jpaUserRole = JPAUserRole.getJPAUserRole(session, role);
		if (jpaUser != null && jpaUserRole != null) {
			jpaUser.getRoles().add(jpaUserRole);
		}
		
		session.getTransaction().commit();
		session.close();
	}

}
